using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Web;
using SinBiota.Model.Business.MapServices.Datatypes;
using SinBiota.Model.Business.MapServices.Interfaces;

namespace SinBiota.Model.Business.MapServices.Implementation
{
    

    public class HeatMapServices : IHeatMapServices
    {
        private const int TileImageSize = 256;

        public Bitmap CreateHeatMap(IList<HeatPoint> heatPoints, Bitmap image)
        {
            // empty image if no points:
            if (heatPoints == null) return image;
            // Call CreateIntensityMask, give it the memory bitmap, and store the result back in the memory bitmap
            image = CreateIntensityMask(image, heatPoints);

            // Colorize the memory bitmap and assign it as the picture boxes image
            return Colorize(image);
        }

        private static Bitmap CreateIntensityMask(Bitmap image, IEnumerable<HeatPoint> heatPoints)
        {
            // Create new graphics surface from memory bitmap
            var drawSurface = Graphics.FromImage(image);

            // Set background color to white so that pixels can be correctly colorized
            drawSurface.Clear(Color.White);

            // Traverse heat point data and draw masks for each heat point
            foreach (var heatPoint in heatPoints)
            {
                // Render current heat point on draw surface
                DrawHeatPoint(drawSurface, heatPoint);
            }

            return image;
        }

        private static void DrawHeatPoint(Graphics graphics, HeatPoint heatPoint)
        {
            // If heatPoint is small, just return;
            if (heatPoint.Radius < 2) return;
            // If it it outside the drawing area, return;
            if (!IsVisibleHeatPoint(heatPoint)) return;

            // Create points generic list of points to hold circumference points
            var circumferencePointsList = new List<Point>();

            // Calculate ratio to scale byte intensity range from 0-255 to 0-1
            const float fRatio = 1F / Byte.MaxValue;
            // Precalulate half of byte max value
            const byte bHalf = Byte.MaxValue / 2;
            // Flip intensity on it's center value from low-high to high-low
            int iIntensity = (byte)(heatPoint.Intensity - ((heatPoint.Intensity - bHalf) * 2));
            // Store scaled and flipped intensity value for use with gradient center location
            var fIntensity = iIntensity * fRatio;

            // Loop through all angles of a circle
            // Define loop variable as a double to prevent casting in each iteration
            // Iterate through loop on 10 degree deltas, this can change to improve performance
            for (double i = 0; i <= 6.28; i += 0.174)
            {
                // Add circumference point to generic point list
                circumferencePointsList.Add(new Point
                                                {
                                                    // Plot new point on the circumference of a circle of the defined radius
                                                    X = Convert.ToInt32(heatPoint.X + heatPoint.Radius*Math.Cos(i)),
                                                    Y = Convert.ToInt32(heatPoint.Y + heatPoint.Radius*Math.Sin(i))
                                                });
            }

            // Create new PathGradientBrush to create a radial gradient using the circumference points
            var gradientShaper = new PathGradientBrush(circumferencePointsList.ToArray());

            // Create new color blend to tell the PathGradientBrush what colors to use and where to put them
            var gradientSpecifications = new ColorBlend(3)
                                             {
                                                 // Define positions of gradient colors, use intesity to adjust the middle color to
                                                 // show more mask or less mask
                                                 Positions = new[] { 0, fIntensity, 1 },
                                                 // Define gradient colors and their alpha values, adjust alpha of gradient colors to match intensity
                                                 Colors = new[]
                                                              {
                                                                  Color.FromArgb(0, Color.White),
                                                                  Color.FromArgb(heatPoint.Intensity, Color.Black),
                                                                  Color.FromArgb(heatPoint.Intensity, Color.Black)
                                                              }
                                             };

            // Pass off color blend to PathGradientBrush to instruct it how to generate the gradient
            gradientShaper.InterpolationColors = gradientSpecifications;

            // Draw polygon (circle) using our point array and gradient brush
            graphics.FillPolygon(gradientShaper, circumferencePointsList.ToArray());
        }

        private static bool IsVisibleHeatPoint(HeatPoint heatPoint)
        {
            if (heatPoint.X + heatPoint.Radius < 0) return false;
            if (heatPoint.X - heatPoint.Radius > TileImageSize) return false;
            if (heatPoint.Y + heatPoint.Radius < 0) return false;
            if (heatPoint.Y - heatPoint.Radius > TileImageSize) return false;
            return true;
        }

        private static Bitmap Colorize(Bitmap mask)
        {
            // Create new bitmap to act as a work surface for the colorization process
            var output = new Bitmap(mask.Width, mask.Height, PixelFormat.Format32bppArgb);
            // Create a graphics object from our memory bitmap so we can draw on it and clear it's drawing surface
            var surface = Graphics.FromImage(output);
            surface.Clear(Color.Transparent);

            // Build an array of color mappings to remap our greyscale mask to full color
            // Accept an alpha byte to specify the transparancy of the output image
            var colors = CreatePaletteIndex();

            // Create new image attributes class to handle the color remappings
            // Inject our color map array to instruct the image attributes class how to do the colorization
            var remapper = new ImageAttributes();
            remapper.SetRemapTable(colors);

            // Transparent color to 'empty regions':
            mask.MakeTransparent(Color.Transparent);

            // Draw our mask onto our memory bitmap work surface using the new color mapping scheme
            surface.DrawImage(mask, new Rectangle(0, 0, mask.Width, mask.Height), 0, 0, mask.Width, mask.Height, GraphicsUnit.Pixel, remapper);

            // Send back newly colorized memory bitmap
            return output;
        }

        private static ColorMap[] CreatePaletteIndex()
        {
            const int alphaFade = 15;
            var outputMap = new ColorMap[256];
            var palette = (Bitmap)Image.FromFile(HttpContext.Current.Server.MapPath("~/Content/img/map/heatpalette.jpg"));
            // Loop through each pixel and create a new color mapping
            for (var i = 0; i <= 255; i++)
            {
                var alpha = 255*(255 - alphaFade - i)/alphaFade + 255;
                if (alpha > 255) alpha = 255;
                outputMap[i] = new ColorMap
                                   {
                                       OldColor = Color.FromArgb(i, i, i),
                                       NewColor = Color.FromArgb(alpha, palette.GetPixel(i, 0))
                                   };
            }

            return outputMap;
        }
    }
}